package de.ret_world.openrpgstudio.editor;

import de.ret_world.openrpgstudio.editor.helpers.ScriptTools;
import de.ret_world.openrpgstudio.editor.script.MainLib;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.luaj.vm2.lib.LibFunction;

/**
 * The ScriptRegister manages all your scripts. Get an instance through the creates Project.
 *
 * @author Emu
 */
public class ScriptRegister {

    private HashMap<String, Path> scripts;
    private HashMap<Integer, ArrayList<String>> priorities;
    private HashMap<String, LibFunction> libs;

    protected ScriptRegister() {
        scripts = new HashMap<>();
        libs = new HashMap<>();
        priorities = new HashMap<>();
        scripts.put("start", Paths.get("start.lua"));
        libs.put("MainLib", new MainLib());
    }

    /**
         * Registers a script file with the specified key in the system. Registered files will be automaticly put into the start.lua script and can be called by other plugins, like the ScriptEditor plugin.
         * @param key the key for the script
         * @param scriptFile the path to the script file
         * @param priority a priority for the execution in the start script. higher values mean later execution.
         */
    public void registerScript(String key, Path scriptFile, int priority) {
        if (scripts.containsKey(key)) {
            throw new RuntimeException("There is already a script registered under the key \"" + key + "\"!");
        }
        scripts.put(key, scriptFile);

        ArrayList<String> lst = priorities.get(priority);
        if (lst == null) {
            lst = new ArrayList<>();
            priorities.put(priority, lst);
        }
        lst.add(key);

        try {
            storeScriptRegister();
        } catch (IOException ex) {
            Logger.getLogger(Project.class).error(ex);
        }
    }

    /**
         * Unregisters the script behind the given key and deletes all links in other scripts or plugins.
         * @param key the key of the script
         */
    public void unregisterScript(String key) {
        scripts.remove(key);

        for (Integer i : priorities.keySet()) {
            priorities.get(i).remove(key);
        }

        try {
            storeScriptRegister();
        } catch (IOException ex) {
            Logger.getLogger(Project.class).error(ex);
        }
    }

    /**
         * Registers a Lua library to the system. Registered libraries will automatically be used when executing the start-script on game launch or when opening the Database Dialog.
         * @param key the key for the library
         * @param lib the library object 
         */
    public void registerLibrary(String key, LibFunction lib) {
        libs.put(key, lib);
    }

    /**
         * Unregisters the library behind the given key and deletes all links in other scripts or plugins.
         * @param key the key of the library
         */
    public void unregisterLibrary(String key) {
        libs.remove(key);
    }

    /**
         * @param key the key of the library
         * @return the library object
         */
    public LibFunction getLibrary(String key) {
        return libs.get(key);
    }

    /**
         * @param key the key of the script
         * @return the relative path to the script file
         */
    public Path getPath(String key) {
        return scripts.get(key);
    }

    /**
         * @param key the key of the script
         * @return the absolute path to the script file
         */
    public Path getAbsolutePath(String key) {
        return Project.getInstance().getFolder("scripts").resolve(scripts.get(key));
    }

    /**
         * @return the script map. you can use this method to get all the registered scripts in the system, but you can also mess them up, so be careful! To add or remove scripts, you should use the apropriate methods!
         */
    public HashMap<String, Path> getScriptMap() {
        return scripts;
    }

    /**
     * @return the library map. you can use this method to get all the registered libraries in the system, but you can also mess them up, so be careful! To add or remove libraries, you should use the apropriate methods!
     */
    public HashMap<String, LibFunction> getLibraries() {
        return libs;
    }

    /**
         * @param key the key of the script
         * @return true if script is registered, false if not
         */
    public boolean isScriptRegistered(String key) {
        return scripts.containsKey(key);
    }

    protected void storeScriptRegister() throws IOException {
        //ScriptRegister
        Properties scriptP = new Properties();
        for (String k : scripts.keySet()) {
            if (!k.equals("start")) {
                Path p = scripts.get(k);
                scriptP.put(k, p.toString());
            }
        }
        scriptP.store(Files.newOutputStream(Project.getInstance().getFolder("root").resolve("scriptregister.properties")), "OpenRPGStudio Data File");

        //Script Priority
        scriptP = new Properties();
        for (Integer i : priorities.keySet()) {
            String s = "";

            if (!priorities.get(i).isEmpty()) {
                for (String k : priorities.get(i)) {
                    s += k + ",";
                }
                s = s.substring(0, s.length() - 1);
            }
            scriptP.setProperty("" + i, s);
        }
        scriptP.store(Files.newOutputStream(Project.getInstance().getFolder("root").resolve("scriptpriorities.properties")), "OpenRPGStudio Data File");

        generateStartScript();
        Logger.getLogger(Project.class).info("Script Register saved successfully!");
    }

    protected void loadScriptRegister() throws IOException {
        //ScriptRegister
        Properties prop = new Properties();
        prop.load(Files.newInputStream(Project.getInstance().getFolder("root").resolve("scriptregister.properties")));

        for (String k : prop.stringPropertyNames()) {
            scripts.put(k, Paths.get(prop.getProperty(k)));
        }

        //Script Priority
        prop = new Properties();
        prop.load(Files.newInputStream(Project.getInstance().getFolder("root").resolve("scriptpriorities.properties")));

        for (String k : prop.stringPropertyNames()) {
            Integer i = Integer.parseInt(k);
            String s = prop.getProperty(k);

            String str[] = s.split(",");

            ArrayList<String> lst = priorities.get(i);
            if (lst == null) {
                lst = new ArrayList<>();
                priorities.put(i, lst);
            }
            for (int j = 0; j < str.length; j++) {
                lst.add(str[j].trim());
            }
        }

        generateStartScript();
        Logger.getLogger(Project.class).info("Script Register loaded successfully!");
    }

    protected void generateStartScript() {
        String script = "-- OpenRPGStudio Start Script --\n"
                + "-- Script is generated by application! Change with care! --\n\n";

        ArrayList<Integer> priorityKeys = new ArrayList<>(priorities.keySet());
        Collections.sort(priorityKeys);

        Path p = Project.getInstance().getMainPath().relativize(Project.getInstance().getFolder("scripts"));

        for (Integer i : priorityKeys) {
            ArrayList<String> keys = priorities.get(i);
            for (String k : keys) {

                if (!k.equals("start")) {
                    script += "dofile(Main.getScriptFolder()..'" + scripts.get(k) + "')\n";
                }
            }
        }

        ScriptTools.getInstance().saveScriptFile(Project.getInstance().getFolder("scripts").resolve(scripts.get("start")), script);
    }
}
